"""
Copyright 2007, Thomas Dejanovic.
 
This is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of
the License, or (at your option) any later version.
 
This software is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
 
You should have received a copy of the GNU Lesser General Public
License along with this software; if not, write to the Free
Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301 USA, or see the FSF site: http://www.fsf.org.

This module defines generic xml-related methods & constants that are useful 
throughout hatch. 
"""

id = "$Id: hatch_xml_methods.py 667 2010-07-01 00:12:17Z jayshurtz $"
# $URL: http://hatch.googlecode.com/svn/tags/taggle_release_2.3/hatch/hatch_library/hatch_xml_methods.py $
# $Author: jayshurtz $
version = " ".join(id.split()[1:3])


import xml.dom.minidom
import to_hatch_structure
from hatch_log import *
from hatch_constants import HATCH_NODE_ATTRIBUTES

# Declare an empty xml document for type checking.
doc = xml.dom.minidom.Document()    # doc.DOCUMENT_NODE, doc.ELEMENT_NODE, doc.TEXT_NODE = (9, 1, 3)

# Define the 'effective' types of xml nodes as far as hatch is concerned.
# These are one-hot in binary so that they may be OR'd together.
UNKNOWN_ERROR = 1       # Undefined type.
DOCUMENT_NODE = 2       # Hatch is interested in this node's child nodes only.
EMPTY_TEXT_NODE = 4     # Hatch ignores these.
HATCH_VALUE = 8         # Value of hatch node property or class attribute, ex: 'name="value"' or '<name> value </name>'.
HATCH_PROPERTY = 16     # Hatch node property, ex: address.
HATCH_ATTRIBUTE = 32    # Hatch node class attribute, ex: name.
HATCH_NODE = 64         # Hatch node, ex: Register(name='counter', bit_width=8).


def get_node_xml(xmlNode):
    """ Get an xml node's xml tag, not including any child tags.
        'xmlNode' is an xml.dom.minidom node instance.
        Returns a string containing the xml tag.
    """
    nodeXml = xmlNode.toxml()
    foundIndex = nodeXml.find('\n')
    if foundIndex > 0:
        nodeXml = nodeXml[:nodeXml.find('\n')]
    return str(nodeXml) # Remove unicode/ convert to ascii.

def get_node_data(xmlNode):
    """ Get various information about an xml node.
        'xmlNode' is an xml.dom.minidom node instance.
        Returns a string containing the information.
    """
    dataString = "\n"
    dataString += "node Type: " + str(xmlNode.nodeType)
    #dataString += "node name: " + xmlNode.nodeName
    #dataString += "node parent: " + str(xmlNode.parentNode)
    #dataString += "node attributes: " + str(xmlNode.attributes)
    if xmlNode.attributes is not None:
    #    dataString += "  node.attributes length: " + str(len(xmlNode.attributes))
        dataString += "  node.attributes.items: " + str(xmlNode.attributes.items())
    #dataString += "node nodeValue: %r" % str(xmlNode.nodeValue)
    hasChildNodes = xmlNode.hasChildNodes()
    #dataString += "node hasChildNodes(): " + str(hasChildNodes)
    if hasChildNodes:
        dataString += "  " + "\n  ".join(["%r, %s" % (str(i), i.nodeType) for i in xmlNode.childNodes])
    return dataString

#------------------------------------------------------------------------------
# The following function[s] are used to search the xml for hatch nodes.
# They may need to be changed if the hatched xml format is changed (see 
# hatch_xml.py).  All higher-level xml functions in hatch should work only 
# through the functions defined in this file.

def get_xml_hatch_node_type(xmlNode):
    """ Get the 'effective' hatch node type of an xml node (as far as hatch 
        is concerned.)
        'xmlNode' is an xml.dom.minidom node instance.
        Returns the 'effective' hatch node type.
    """
    hatch_node_type = UNKNOWN_ERROR
    if xmlNode.nodeType == doc.DOCUMENT_NODE:   # or xmlNode.nodeType == doc.PROCESSING_INSTRUCTION_NODE:    # or <?xml version="1.0" ?>
        hatch_node_type = DOCUMENT_NODE
    elif xmlNode.nodeType == doc.TEXT_NODE:
        nodeValue = xmlNode.nodeValue.strip()
        if len(nodeValue) < 1:
            hatch_node_type = EMPTY_TEXT_NODE
        else:
            hatch_node_type = HATCH_VALUE
    elif xmlNode.nodeType == doc.ELEMENT_NODE:
        childNodeTypes = [i.nodeType for i in xmlNode.childNodes]
        # Has 1 child node of type text node & attributes of len 0 (name CAN be in attributes.) 
        # This node doesn't have properties, it IS a property. 
        # Note: hatched XML tags are NOT allowed to have BOTH attributes and values like this: <node attribute='a'> value </node>
        if len(xmlNode.attributes) == 0 and doc.TEXT_NODE in childNodeTypes and len(childNodeTypes) == 1:
            if str(xmlNode.nodeName) in HATCH_NODE_ATTRIBUTES:
                hatch_node_type = HATCH_ATTRIBUTE
            else:
                hatch_node_type = HATCH_PROPERTY
        # Multiple child nodes of type element node (and text node) OR 1+ attributes (name CAN be in attributes.)
        # This node is probably a named node with properties and/or child nodes.
        # TODO Also check if xml tag value class has HatchNode in __bases__ ?
        elif len(xmlNode.attributes) > 0 or doc.ELEMENT_NODE in childNodeTypes:
            hatch_node_type = HATCH_NODE
    return hatch_node_type

def next_xml_hatch_node_tag(xmlNode):
    """ Find and return the next XML tag that represents a hatch node.
        This may be the passed in node, or one of its nested child nodes.
        This is used to handle/check document nodes and skip property and 
        text nodes.
        'xmlNode' is an xml.dom.minidom node instance.
        Returns 'None' if not found, or an xml.dom.minidom node instance.
    """
    retVal = None
    effective_type = get_xml_hatch_node_type(xmlNode)
    if effective_type == DOCUMENT_NODE: # Encountered the outer 'document' node.
        if len(xmlNode.childNodes) != 1:
            raise AssertionError, hatchNodeErrorString(hatchNode, "Hatched XML must have exactly one top node tag.")
        else:
            logger.debug("Skipping document node & recursing into child nodes.")
            retVal = next_xml_hatch_node_tag(xmlNode.childNodes[0])
    elif effective_type == EMPTY_TEXT_NODE or effective_type == HATCH_VALUE:
        logger.debug('Skipping text value node.')   # Text nodes cannot nest hatch nodes.
    elif effective_type == HATCH_PROPERTY or effective_type == HATCH_ATTRIBUTE:
        logger.debug('Skipping property/attribute node.')   # Attribute nodes cannot nest hatch nodes.
    elif effective_type == HATCH_NODE:
        retVal = xmlNode    # The passed-in node represents a hatch node.
    else:
        raise AssertionError, "Unhandled node type: " + str(node.nodeType) + ", " + nodeXml
    return retVal

def get_xml_hatch_child_nodes(xmlNode):
    """ Get the XML tags that represent hatch nodes that are child nodes
        of the hatch node xml tag that is passed in.
        'xmlNode' is an xml.dom.minidom node instance for which 
            'get_xml_hatch_node_type()' should return 'HATCH_NODE'.
        Returns a list of xml.dom.minidom instances representing hatch nodes.
    """
    return [childNode for childNode in xmlNode.childNodes if get_xml_hatch_node_type(childNode) == HATCH_NODE]

class HatchXmlIterator(object): # TODO Track returned parent, otherwise this is just a nice example.
    """ Depth first iterator for hatched XML.
        This iterator tries to be efficient by ONLY iterating through the XML
        tags that represent hatch nodes (skipping document & text nodes.)
    """
    def __init__(self, xmlNode, parent=None):
        """ Add the first XML tag encountered that represents a hatch node to 
            the stack.  This may or may not be the node that is passed in.
            'xmlNode' is an xml.dom.minidom node instance.
            'parent' stores the parent instance when changing depths.
        """
        self.stack = []
        self.parent = parent
        xmlHatchNode = next_xml_hatch_node_tag(xmlNode)
        if xmlHatchNode is not None:
            self.stack.append(xmlHatchNode)
    def __iter__(self):
        return self
    def next(self):
        if len(self.stack) == 0:
            raise StopIteration
        xmlHatchNode = self.stack.pop(-1)
        childNodes = get_xml_hatch_child_nodes(xmlHatchNode)
        for index in xrange(len(childNodes)-1, -1, -1): # Add to stack in REVERSE order.
            self.stack.append(childNodes[index])
        return xmlHatchNode

#------------------------------------------------------------------------------
# The following funciton[s] are used to get hatch node properties from xml.
# They may need to be changed if the hatched xml format is changed.

def get_xml_hatch_detail_nodes(xmlNode):
    """ Get the XML tags that represent the properties and attributes of
        of the hatch node xml tag that is passed in.
        'xmlNode' is an xml.dom.minidom node instance for which 
            'get_xml_hatch_node_type()' should return 'HATCH_NODE'.
        Returns a list of xml.dom.minidom instances representing hatch details.
    """
    #... > 0 and get_xml_hatch_node_type(childNode.childNodes[0]) == HATCH_VALUE]   # Skip EMPTY_TEXT_NODE type ?
    return [childNode for childNode in xmlNode.childNodes if get_xml_hatch_node_type(childNode) & (HATCH_PROPERTY | HATCH_ATTRIBUTE) > 0]

def get_xml_hatch_node_details(xmlNode):
    """ Get hatch node attributes & properties from an xml node as dictionaries.
        Gets the items '<Node key1="value1" key2="value2"> ... </Node>' 
        as '{key: value, key: value}'.
        'xmlNode' is an xml.dom.minidom node instance for which 
            'get_xml_hatch_node_type()' should return 'HATCH_NODE'.
        Returns the attributes & properties dictionaries.
    """
    xml_properties = dict([(key, eval(value)) for (key, value) in xmlNode.attributes.items()])
    for childNode in get_xml_hatch_detail_nodes(xmlNode):
        if xml_properties.has_key(childNode.nodeName):
            raise AssertionError, 'Hatched XML tags cannot have attributes and child tags with the same name: ' + xmlNode.nodeName + ", " + childNode.nodeName
        xml_properties[str(childNode.nodeName)] = eval(childNode.childNodes[0].nodeValue.strip())  # Get the items '<key> value </key>' as '[key] = value'. eval() evaluates a string.
    xml_attributes = []
    for key in HATCH_NODE_ATTRIBUTES:
        try:
            value = xml_properties.pop(key)
        except KeyError, e:
            pass    # Skip key if not in xml.
        else:
            xml_attributes.append((key, value))
    xml_attributes = dict(xml_attributes)
    return xml_attributes, xml_properties


# For testing.
if __name__ == "__main__":

    from hatch_node import HatchNode
    consoleHandler.setLevel(logging.DEBUG)

    xml1 = """<?xml version="1.0" ?>
        <Struct name="'test_000'" parent="None">
            <target>
                "small apb slave"
            </target>
            <packed>
                True
            </packed>
            <hatch_leaf>
                False
            </hatch_leaf>
            <Register name="'register_a'">
                <comment>
                    "This is a register"
                </comment>
                <bit_width>
                    32
                </bit_width>
                <default>
                    0
                </default>
                <sw>
                    "rw"
                </sw>
                <hw>
                    "r"
                </hw>
                <hatch_leaf>
                    True
                </hatch_leaf>
                <packed>
                    True
                </packed>
            </Register>
            <Register name="'register_b'" parent="'test_000'">
                <comment>
                    "This is a register"
                </comment>
                <bit_width>
                    32
                </bit_width>
                <default>
                    0
                </default>
                <sw>
                    "rw"
                </sw>
                <hw>
                    "r"
                </hw>
                <hatch_leaf>
                    True
                </hatch_leaf>
                <packed>
                    True
                </packed>
            </Register>
        </Struct>
        """

    if False:
        def testRecurse(xmlNode):
            print "node xml: " + repr(get_node_xml(xmlNode))
            #print "node data: " + repr(get_node_data(xmlNode))
            hatch_node_type = get_xml_hatch_node_type(xmlNode)
            print "hatch_node_type: " + str(hatch_node_type)
            if hatch_node_type == HATCH_NODE:
                print get_xml_hatch_node_details(xmlNode)
                item = xml_node_to_hatch_node(xmlNode)
                print item.info()

            #item = parse_xml(xmlNode, xml_node_to_hatch_node)
            #print "parse_xml: " + str(item)
            #if item is not None:
            #    print item.info()

            raw_input()
            for childNode in xmlNode.childNodes:
                testRecurse(childNode)

        testRecurse(xml.dom.minidom.parseString(xml1))


    if False:
        xml_bad = """<?xml version="1.0" ?>
            <comment>
                "This is a register"
            </comment>
            <bit_width>
                32
            </bit_width>
            <default>
                0
            </default>
            <sw>
                "rw"
            </sw>
            <hw>
                "r"
            </hw>
            <hatch_leaf>
                True
            </hatch_leaf>
            <packed>
                True
            </packed>
            """
        hatchNode = hatchify_xml(xml.dom.minidom.parseString(xml_bad))
        print hatchNode.info()

    if True:
        dom = xml.dom.minidom.parseString(xml1)
        print '~~~'
        print get_xml_hatch_node_type(dom)
        print get_xml_hatch_node_type(dom.childNodes[0])
        print get_xml_hatch_node_type(dom.childNodes[0].childNodes[0])
        print '~~~'
        print next_xml_hatch_node_tag(dom)
        print next_xml_hatch_node_tag(dom.childNodes[0])
        print next_xml_hatch_node_tag(dom.childNodes[0].childNodes[1])
        print next_xml_hatch_node_tag(dom.childNodes[0].childNodes[7])
        print '~~~'
        print get_xml_hatch_child_nodes(dom)
        print get_xml_hatch_child_nodes(dom.childNodes[0])
        print next_xml_hatch_node_tag(dom.childNodes[0].childNodes[1])
        print get_xml_hatch_child_nodes(dom.childNodes[0].childNodes[7])
        print '~~~'
        for node in HatchXmlIterator(dom):
            print get_node_xml(node)
        print '~~~'
            
